package edu.northwestern.cbits.purple_robot_manager.probes.studies;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.calibration.PebbleCalibrationHelper;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityCheck;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
public class LivewellPebbleActivityCountsProbe extends Probe
{
public final static String PROBE_NAME = "edu.northwestern.cbits.purple_robot_manager.probes.studies.LivewellPebbleActivityCountsProbe";
private static final String FIRMWARE_VERSION = "FIRMWARE_VERSION";
private static final byte COMMAND_FLUSH_BUFFER = 0x00;
private static final String BUNDLE_NUM_SAMPLES = "BUNDLE_NUM_SAMPLES";
private static final String BUNDLE_BATTERY_LEVEL = "BUNDLE_BATTERY_LEVEL";
private static final String BUNDLE_IS_CHARGING = "BUNDLE_IS_CHARGING";
private static final String FREQUENCY = "config_probe_livewell_pebble_frequency";
private static final String BUNDLE_DIFF_MEANS = "BUNDLE_DIFF_MEANS";
private static final int CURRENT_VERSION = 0x03;
private static UUID WATCHAPP_UUID = UUID.fromString("09e5f53c-651e-408a-8b10-3b5b0e1b6b09");
public static final String ENABLED = "config_probe_livewell_pebble_enabled";
public static final boolean DEFAULT_ENABLED = false;
private PebbleKit.PebbleDataReceiver _receiver = null;
private PebbleKit.PebbleNackReceiver _nackReceiver = null;
private PebbleKit.PebbleAckReceiver _ackReceiver = null;
private long _lastRefresh = 0;
private final ArrayList<Bundle> _pendingReadings = new ArrayList<>();
private boolean _isTransmitting = false;
@Override
public String getPreferenceKey() {
return "services_livewell_pebble";
}
private static class ActivityCount
{
// Inspired by https://github.com/kramimus/pebble-accel-analyzer
private long start = 0;
private short numSamples = 0;
private int mean = 0;
private byte battery = 0;
public ActivityCount(byte[] data)
{
for (int i = 0; i < 8; i++)
{
this.start |= ((long) (data[i] & 0xff)) << (i * 8);
}
for (int i = 0; i < 2; i++)
{
this.numSamples |= ((short) (data[i + 8] & 0xff)) << (i * 8);
}
for (int i = 0; i < 4; i++)
{
this.mean |= (data[i + 10] & 0xff) << (i * 8);
}
this.battery = data[14];
}
public int mean()
{
return this.mean;
}
@SuppressWarnings("unused")
public JSONObject toJson(Context context)
{
JSONObject json = new JSONObject();
try
{
json.put("start", this.start);
json.put("num_samples", this.numSamples);
json.put("mean", this.mean);
json.put("battery_level", this.batteryLevel());
json.put("is_charging", this.isCharging());
return json;
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return null;
}
public boolean isCharging()
{
return (this.battery & 0x80) == 0x80;
}
public int batteryLevel()
{
return (int) this.battery & 0x7f;
}
public long start()
{
return this.start;
}
public int numSamples()
{
return this.numSamples;
}
public void applyTimezone(TimeZone tz)
{
this.start -= tz.getOffset(this.start);
}
}
@Override
public String name(Context context)
{
return "edu.northwestern.cbits.purple_robot_manager.probes.studies.LivewellPebbleActivityCountsProbe";
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_studies_category);
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(final Context context, PreferenceManager manager)
{
final LivewellPebbleActivityCountsProbe me = this;
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(R.string.summary_livewell_pebble_probe_desc);
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(LivewellPebbleActivityCountsProbe.ENABLED);
enabled.setDefaultValue(LivewellPebbleActivityCountsProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(LivewellPebbleActivityCountsProbe.FREQUENCY);
duration.setDefaultValue(Probe.DEFAULT_FREQUENCY);
duration.setEntryValues(R.array.probe_satellite_frequency_values);
duration.setEntries(R.array.probe_satellite_frequency_labels);
duration.setTitle(R.string.probe_fetch_rate_label);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.contains(LivewellPebbleActivityCountsProbe.FREQUENCY))
{
try
{
prefs.getString(LivewellPebbleActivityCountsProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY);
}
catch (ClassCastException e)
{
long freq = prefs.getLong(LivewellPebbleActivityCountsProbe.FREQUENCY, 0);
Editor ed = prefs.edit();
ed.putString(LivewellPebbleActivityCountsProbe.FREQUENCY, "" + freq);
ed.commit();
}
}
screen.addPreference(duration);
Preference fetchNow = new Preference(context);
fetchNow.setTitle(R.string.action_request_data);
fetchNow.setSummary(R.string.action_desc_request_data);
fetchNow.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener()
{
public boolean onPreferenceClick(Preference preference)
{
me._lastRefresh = 0;
me.isEnabled(context);
return true;
}
});
screen.addPreference(fetchNow);
Preference installWatchApp = new Preference(context);
installWatchApp.setTitle(R.string.probe_livewell_pebble_install_label);
installWatchApp.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.probe_livewell_pebble_install_url))));
screen.addPreference(installWatchApp);
return screen;
}
public String getMainScreenAction(Context context)
{
return context.getString(R.string.action_request_data_no_now);
}
public void runMainScreenAction(Context context)
{
this._lastRefresh = 0;
this.isEnabled(context);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(LivewellPebbleActivityCountsProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(LivewellPebbleActivityCountsProbe.ENABLED, false);
e.commit();
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
JSONArray values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_satellite_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
@Override
public boolean isEnabled(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
if (super.isEnabled(context))
{
if (prefs.getBoolean(LivewellPebbleActivityCountsProbe.ENABLED, LivewellPebbleActivityCountsProbe.DEFAULT_ENABLED))
{
PebbleCalibrationHelper.check(context, true);
PebbleKit.startAppOnPebble(context, LivewellPebbleActivityCountsProbe.WATCHAPP_UUID);
if (this._receiver == null)
{
final LivewellPebbleActivityCountsProbe me = this;
this._receiver = new PebbleKit.PebbleDataReceiver(LivewellPebbleActivityCountsProbe.WATCHAPP_UUID)
{
public void receiveData(final Context context, final int transactionId, final PebbleDictionary dictionary)
{
PebbleKit.sendAckToPebble(context, transactionId);
SanityManager sanity = SanityManager.getInstance(context);
String obsoleteTitle = me.title(context);
byte[] payload = dictionary.getBytes(LivewellPebbleActivityCountsProbe.CURRENT_VERSION);
if (payload != null && payload.length <= 15)
{
ActivityCount count = new ActivityCount(payload);
TimeZone here = Calendar.getInstance().getTimeZone();
count.applyTimezone(here);
Bundle data = new Bundle();
data.putDouble(Probe.BUNDLE_TIMESTAMP, count.start() / 1000);
data.putString(Probe.BUNDLE_PROBE, me.name(context));
PebbleKit.FirmwareVersionInfo info = PebbleKit.getWatchFWVersion(context);
if (info != null)
data.putString(LivewellPebbleActivityCountsProbe.FIRMWARE_VERSION, "" + info.getMajor() + "." + info.getMinor() + "." + info.getPoint());
data.putInt(LivewellPebbleActivityCountsProbe.BUNDLE_NUM_SAMPLES, count.numSamples());
data.putInt(LivewellPebbleActivityCountsProbe.BUNDLE_DIFF_MEANS, count.mean());
data.putInt(LivewellPebbleActivityCountsProbe.BUNDLE_BATTERY_LEVEL, count.batteryLevel());
data.putBoolean(LivewellPebbleActivityCountsProbe.BUNDLE_IS_CHARGING, count.isCharging());
synchronized (me._pendingReadings) {
me._pendingReadings.add(data);
}
Runnable r = new Runnable() {
// Delay immediate transmission so display system can keep up - one reading per second.
public void run() {
if (me._isTransmitting)
return;
me._isTransmitting = true;
while (me._pendingReadings.size() > 0)
{
synchronized (me._pendingReadings)
{
if (me._pendingReadings.size() > 0)
{
me.transmitData(context, me._pendingReadings.get(0));
me._pendingReadings.remove(0);
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogManager.getInstance(context).logException(e);
}
}
me._isTransmitting = false;
}
};
Thread t = new Thread(r);
t.start();
sanity.clearAlert(obsoleteTitle);
}
else
{
String obsoleteWarning = context.getString(R.string.message_warning_obsolete_livewell_watchface);
final Context appContext = context.getApplicationContext();
Runnable r = new Runnable()
{
public void run()
{
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(appContext.getString(R.string.probe_livewell_pebble_install_url)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(intent);
}
};
sanity.addAlert(SanityCheck.WARNING, obsoleteTitle, obsoleteWarning, r);
}
}
};
PebbleKit.registerReceivedDataHandler(context, this._receiver);
}
long now = System.currentTimeMillis();
long freq = 0;
try
{
freq = Long.parseLong(prefs.getString(LivewellPebbleActivityCountsProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
}
catch (ClassCastException e)
{
freq = prefs.getLong(LivewellPebbleActivityCountsProbe.FREQUENCY, Long.parseLong(Probe.DEFAULT_FREQUENCY));
}
if (now - this._lastRefresh > freq)
{
this._lastRefresh = now;
if (this._ackReceiver == null)
{
this._ackReceiver = new PebbleKit.PebbleAckReceiver(LivewellPebbleActivityCountsProbe.WATCHAPP_UUID)
{
public void receiveAck(Context context, int i)
{
// Log.e("PR", "RECV ACK: " + i);
}
};
PebbleKit.registerReceivedAckHandler(context, this._ackReceiver);
}
if (this._nackReceiver == null)
{
this._nackReceiver = new PebbleKit.PebbleNackReceiver(LivewellPebbleActivityCountsProbe.WATCHAPP_UUID)
{
public void receiveNack(Context context, int i)
{
// Log.e("PR", "RECV NAK: " + i);
}
};
PebbleKit.registerReceivedNackHandler(context, this._nackReceiver);
}
PebbleDictionary data = new PebbleDictionary();
data.addUint8(0x00, LivewellPebbleActivityCountsProbe.COMMAND_FLUSH_BUFFER);
PebbleKit.sendDataToPebble(context, LivewellPebbleActivityCountsProbe.WATCHAPP_UUID, data);
}
return true;
}
}
PebbleCalibrationHelper.check(context, false);
if (this._receiver != null)
{
try
{
context.unregisterReceiver(this._receiver);
}
catch (IllegalArgumentException e)
{
// Do nothing - receiver not registered...
}
this._receiver = null;
}
if (this._nackReceiver != null)
{
try
{
context.unregisterReceiver(this._nackReceiver);
}
catch (IllegalArgumentException e)
{
// Do nothing - receiver not registered...
}
this._nackReceiver = null;
}
if (this._ackReceiver != null)
{
try
{
context.unregisterReceiver(this._ackReceiver);
}
catch (IllegalArgumentException e)
{
// Do nothing - receiver not registered...
}
this._ackReceiver = null;
}
PebbleCalibrationHelper.check(context, false);
return false;
}
public String summary(Context context)
{
return context.getString(R.string.summary_livewell_pebble_probe_desc);
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_livewell_pebble_probe);
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
double count = bundle.getDouble(LivewellPebbleActivityCountsProbe.BUNDLE_DIFF_MEANS);
double numSamples = bundle.getDouble(LivewellPebbleActivityCountsProbe.BUNDLE_NUM_SAMPLES);
return String.format(context.getResources().getString(R.string.summary_livewell_pebble_probe), numSamples, count);
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if (frequency instanceof Double)
{
frequency = ((Double) frequency).longValue();
}
if (frequency instanceof Long)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(LivewellPebbleActivityCountsProbe.FREQUENCY, frequency.toString());
e.commit();
}
}
}
}